﻿Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports Microsoft.Xna.Framework
Imports Microsoft.Xna.Framework.Audio
Imports Microsoft.Xna.Framework.Content
Imports Microsoft.Xna.Framework.GamerServices
Imports Microsoft.Xna.Framework.Graphics
Imports Microsoft.Xna.Framework.Input
Imports Microsoft.Xna.Framework.Media


Public Structure VertexMultiTextured
    Public Position As Vector3
    Public Normal As Vector3
    Public TextureCoordinate As Vector4
    Public TexWeights As Vector4
    Public Shared SizeInBytes As Integer = (3 + 3 + 4 + 4) * 4
    Public Shared VertexElements As VertexElement() = New VertexElement() _
            {
                New VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                New VertexElement(4 * 3, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0),
                New VertexElement(4 * 6, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 0),
                New VertexElement(4 * 10, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 1)
            }
End Structure

Public Class Game2
    Inherits Microsoft.Xna.Framework.Game

    Private WithEvents graphics As GraphicsDeviceManager
    Private Device As GraphicsDevice

    Private terrainWidth As Integer
    Private terrainLength As Integer
    Private heightData(,) As Single

    Private terrainVertexBuffer As VertexBuffer
    Private terrainIndexBuffer As IndexBuffer
    Private terrainVertexDeclaration As VertexDeclaration

    Private waterVertexBuffer As VertexBuffer
    Private waterVertexDeclaration As VertexDeclaration

    Private treeVertexBuffer As VertexBuffer
    Private treeVertexDeclaration As VertexDeclaration

    Private vertices() As VertexMultiTextured
    Private indices() As Integer

    Private effect As Effect
    Private bbEffect As Effect
    Private viewMatrix As Matrix
    Private projectionMatrix As Matrix
    Private reflectionViewMatrix As Matrix

    Private cameraPosition As Vector3 = New Vector3(130, 30, -50)
    Private leftrightRot As Single = MathHelper.PiOver2
    Private updownRot As Single = -MathHelper.Pi / 10
    Private rotationSpeed As Single = 0.02
    Private moveSpeed As Single = 30
    Private originalMouseState As MouseState

    Private grassTexture As Texture2D
    Private sandTexture As Texture2D
    Private rockTexture As Texture2D
    Private snowTexture As Texture2D
    Private cloudMap As Texture2D
    Private waterBumpMap As Texture2D
    Private treeTexture As Texture2D
    Private treeMap As Texture2D

    Private skyDome As Model

    Private waterHeight As Single = 5
    Private refractionRenderTarget As RenderTarget2D
    Private refractionMap As Texture2D
    Private reflectionRenderTarget As RenderTarget2D
    Private reflectionMap As Texture2D
    Private cloudsRenderTarget As RenderTarget2D
    Private cloudStaticMap As Texture2D
    Private fullScreenVertices() As VertexPositionTexture
    Private fullScreenVertexDeclaration As VertexDeclaration
    Private fullScreenVertexBuffer As VertexBuffer

    Private windDirection As Vector3 = New Vector3(1, 0, 0)
    Private SkyTopColor As Vector4 = New Vector4(1, 0, 0, 1)
    Private SkyBottomColor As Vector4 = New Vector4(0.8, 0, 0, 1)
    Private AmbientLightValue As Single = 0.4

    Public Sub New()
        graphics = New GraphicsDeviceManager(Me)
        Content.RootDirectory = "Content"
    End Sub

    ''' <summary>
    ''' Allows the game to perform any initialization it needs to before starting to run.
    ''' This is where it can query for any required services and load any non-graphic
    ''' related content.  Calling MyBase.Initialize will enumerate through any components
    ''' and initialize them as well.
    ''' </summary>
    Protected Overrides Sub Initialize()
        ' TODO: Add your initialization logic here

        IsMouseVisible = True
        graphics.PreferredBackBufferWidth = 1280
        graphics.PreferredBackBufferHeight = 768
        'graphics.PreferredBackBufferWidth = 500
        'graphics.PreferredBackBufferHeight = 500
        graphics.IsFullScreen = True
        graphics.ApplyChanges()

        Window.Title = "Reimer's XNA Tutorials -- Series 4"

        MyBase.Initialize()
    End Sub

    ''' <summary>
    ''' LoadContent will be called once per game and is the place to load
    ''' all of your content.
    ''' </summary>
    Protected Overrides Sub LoadContent()
        ' Create a new SpriteBatch, which can be used to draw textures.
        'spriteBatch = New SpriteBatch(GraphicsDevice)

        Device = GraphicsDevice
        effect = Content.Load(Of Effect)("Series4Effects3")
        bbEffect = Content.Load(Of Effect)("bbEffect")
        UpdateViewMatrix()
        'viewMatrix = Matrix.CreateLookAt(New Vector3(130, 30, -50), New Vector3(0, 0, -40), New Vector3(0, 1, 0))
        projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, Device.Viewport.AspectRatio, 0.3, 1000)
        Mouse.SetPosition(Device.Viewport.Width / 2, Device.Viewport.Height / 2)
        originalMouseState = Mouse.GetState
        skyDome = Content.Load(Of Model)("dome")
        skyDome.Meshes(0).MeshParts(0).Effect = effect.Clone
        Dim pp As PresentationParameters = Device.PresentationParameters
        refractionRenderTarget = New RenderTarget2D(Device, pp.BackBufferWidth, pp.BackBufferHeight, False, pp.BackBufferFormat, pp.DepthStencilFormat)
        reflectionRenderTarget = New RenderTarget2D(Device, pp.BackBufferWidth, pp.BackBufferHeight, False, pp.BackBufferFormat, pp.DepthStencilFormat)
        LoadVertices()
        LoadTextures()

        ' TODO: use Me.Content to load your game content here
    End Sub

    Private Sub LoadVertices()
        Dim heightMap As Texture2D = Content.Load(Of Texture2D)("heightMap2")
        LoadHeightData(heightMap)
        Dim terrainVertices() As VertexMultiTextured = SetUpTerrainVertices()
        Dim terrainIndices() As Integer = SetUpTerrainIndices()
        terrainVertices = CalculateNormals(terrainVertices, terrainIndices)
        CopyToTerrainBuffers(terrainVertices, terrainIndices)
        SetUpWaterVertices()
        treeMap = Content.Load(Of Texture2D)("treeMap")
        Dim treeList As List(Of Vector3) = GenerateTreePositions(treeMap, terrainVertices)
        CreateBillboardVerticesFromList(treeList)
        fullScreenVertices = SetUpFullscreenVertices()
    End Sub

    Private Sub LoadTextures()
        grassTexture = Content.Load(Of Texture2D)("grass")
        sandTexture = Content.Load(Of Texture2D)("sand")
        rockTexture = Content.Load(Of Texture2D)("rock")
        snowTexture = Content.Load(Of Texture2D)("snow")
        cloudMap = Content.Load(Of Texture2D)("cloudMap")
        waterBumpMap = Content.Load(Of Texture2D)("waterbump")
        treeMap = Content.Load(Of Texture2D)("treeMap")
        treeTexture = Content.Load(Of Texture2D)("tree")
        cloudStaticMap = CreateStaticMap(32)
    End Sub

    Private Sub LoadHeightData(heightMap As Texture2D)
        Dim x As Integer, y As Integer
        Dim minimumHeight As Single = Single.MaxValue
        Dim maximumHeight As Single = Single.MinValue
        terrainWidth = heightMap.Width
        terrainLength = heightMap.Height
        Dim heightMapColors As Color() = New Color((terrainWidth * terrainLength) - 1) {}
        heightMap.GetData(Of Color)(heightMapColors)
        ReDim heightData(terrainWidth, terrainLength)
        For x = 0 To terrainWidth - 1
            For y = 0 To terrainLength - 1
                heightData(x, y) = heightMapColors(x + y * terrainWidth).R
                If heightData(x, y) < minimumHeight Then
                    minimumHeight = heightData(x, y)
                End If
                If heightData(x, y) > maximumHeight Then
                    maximumHeight = heightData(x, y)
                End If
            Next y
        Next x
        For x = 0 To terrainWidth - 1
            For y = 0 To terrainLength - 1
                heightData(x, y) = (heightData(x, y) - minimumHeight) / (maximumHeight - minimumHeight) * 30
            Next y
        Next x
    End Sub

    Private Function SetUpTerrainVertices() As VertexMultiTextured()
        Dim x As Integer, y As Integer
        Dim total As Single
        Dim terrainVertices As VertexMultiTextured() = New VertexMultiTextured(terrainWidth * terrainLength) {}
        vertices = New VertexMultiTextured(terrainWidth * terrainLength) {}
        For x = 0 To terrainWidth - 1
            For y = 0 To terrainLength - 1
                terrainVertices(x + y * terrainWidth).Position = New Vector3(x, heightData(x, y), -y)
                terrainVertices(x + y * terrainWidth).TextureCoordinate.X = CSng(x / 30)
                terrainVertices(x + y * terrainWidth).TextureCoordinate.Y = CSng(y / 30)
                terrainVertices(x + y * terrainWidth).TexWeights.X = MathHelper.Clamp(1 - Math.Abs(heightData(x, y) - 0) / 8, 0, 1)
                terrainVertices(x + y * terrainWidth).TexWeights.Y = MathHelper.Clamp(1 - Math.Abs(heightData(x, y) - 12) / 6, 0, 1)
                terrainVertices(x + y * terrainWidth).TexWeights.Z = MathHelper.Clamp(1 - Math.Abs(heightData(x, y) - 20) / 6, 0, 1)
                terrainVertices(x + y * terrainWidth).TexWeights.W = MathHelper.Clamp(1 - Math.Abs(heightData(x, y) - 30) / 6, 0, 1)
                total = terrainVertices(x + y * terrainWidth).TexWeights.X
                total = total + terrainVertices(x + y * terrainWidth).TexWeights.Y
                total = total + terrainVertices(x + y * terrainWidth).TexWeights.Z
                total = total + terrainVertices(x + y * terrainWidth).TexWeights.W
                terrainVertices(x + y * terrainWidth).TexWeights.X = terrainVertices(x + y * terrainWidth).TexWeights.X / total
                terrainVertices(x + y * terrainWidth).TexWeights.Y = terrainVertices(x + y * terrainWidth).TexWeights.Y / total
                terrainVertices(x + y * terrainWidth).TexWeights.Z = terrainVertices(x + y * terrainWidth).TexWeights.Z / total
                terrainVertices(x + y * terrainWidth).TexWeights.W = terrainVertices(x + y * terrainWidth).TexWeights.W / total
            Next y
        Next x
        Return terrainVertices
    End Function

    Private Function SetUpTerrainIndices() As Integer()
        Dim x As Integer, y As Integer
        Dim counter As Integer = -1
        Dim lowerLeft As Integer
        Dim lowerRight As Integer
        Dim topLeft As Integer
        Dim topRight As Integer
        ReDim indices((terrainWidth - 1) * (terrainLength - 1) * 6)
        For x = 0 To terrainWidth - 2
            For y = 0 To terrainLength - 2
                lowerLeft = x + y * terrainWidth
                lowerRight = (x + 1) + y * terrainWidth
                topLeft = x + (y + 1) * terrainWidth
                topRight = (x + 1) + (y + 1) * terrainWidth
                counter = counter + 1
                indices(counter) = topLeft
                counter = counter + 1
                indices(counter) = lowerRight
                counter = counter + 1
                indices(counter) = lowerLeft
                counter = counter + 1
                indices(counter) = topLeft
                counter = counter + 1
                indices(counter) = topRight
                counter = counter + 1
                indices(counter) = lowerRight
            Next y
        Next x
        Return indices
    End Function

    Private Function CalculateNormals(Vertices As VertexMultiTextured(), Indices As Integer()) As VertexMultiTextured()
        Dim i As Integer
        Dim index1 As Integer
        Dim index2 As Integer
        Dim index3 As Integer
        Dim side1 As Vector3
        Dim side2 As Vector3
        Dim normal As Vector3
        For i = 0 To Vertices.Length - 1
            Vertices(i).Normal = New Vector3(0, 0, 0)
        Next i
        For i = 0 To (Indices.Length / 3) - 1
            index1 = Indices(i * 3)
            index2 = Indices(i * 3 + 1)
            index3 = Indices(i * 3 + 2)
            side1 = Vertices(index1).Position - Vertices(index3).Position
            side2 = Vertices(index1).Position - Vertices(index2).Position
            normal = Vector3.Cross(side1, side2)
            Vertices(index1).Normal = Vertices(index1).Normal + normal
            Vertices(index2).Normal = Vertices(index2).Normal + normal
            Vertices(index3).Normal = Vertices(index3).Normal + normal
        Next i
        For i = 0 To Vertices.Length - 1
            Vertices(i).Normal.Normalize()
        Next i
        Return Vertices
    End Function

    Private Sub CopyToTerrainBuffers(Vertices As VertexMultiTextured(), Indices As Integer())
        Dim VertexDeclaration As VertexDeclaration = New VertexDeclaration(VertexMultiTextured.VertexElements)
        terrainVertexBuffer = New VertexBuffer(Device, VertexDeclaration, Vertices.Length, BufferUsage.WriteOnly)
        terrainVertexBuffer.SetData(Of VertexMultiTextured)(Vertices.ToArray)
        terrainIndexBuffer = New IndexBuffer(Device, IndexElementSize.ThirtyTwoBits, Indices.Length, BufferUsage.WriteOnly)
        terrainIndexBuffer.SetData(Indices)
    End Sub

    Private Sub SetUpWaterVertices()
        Dim Vectors(3) As Vector3
        Vectors(0) = New Vector3(0 - terrainWidth, waterHeight, 0 + terrainLength)
        Vectors(1) = New Vector3(terrainWidth * 2, waterHeight, 0 + terrainLength)
        Vectors(2) = New Vector3(0 - terrainWidth, waterHeight, -terrainLength * 2)
        Vectors(3) = New Vector3(terrainWidth * 2, waterHeight, -terrainLength * 2)
        Dim waterVertices(5) As VertexPositionTexture

        waterVertices(0) = New VertexPositionTexture(Vectors(0), New Vector2(0, 0))
        waterVertices(1) = New VertexPositionTexture(Vectors(3), New Vector2(1, 1))
        waterVertices(2) = New VertexPositionTexture(Vectors(1), New Vector2(1, 0))
        waterVertices(3) = New VertexPositionTexture(Vectors(0), New Vector2(0, 0))
        waterVertices(4) = New VertexPositionTexture(Vectors(2), New Vector2(0, 1))
        waterVertices(5) = New VertexPositionTexture(Vectors(3), New Vector2(1, 1))

        'waterVertices(0) = New VertexPositionTexture(New Vector3(0, waterHeight, 0), New Vector2(0, 1))
        'waterVertices(2) = New VertexPositionTexture(New Vector3(terrainWidth, waterHeight, -terrainLength), New Vector2(1, 0))
        'waterVertices(1) = New VertexPositionTexture(New Vector3(0, waterHeight, -terrainLength), New Vector2(0, 0))
        'waterVertices(3) = New VertexPositionTexture(New Vector3(0, waterHeight, 0), New Vector2(0, 1))
        'waterVertices(5) = New VertexPositionTexture(New Vector3(terrainWidth, waterHeight, 0), New Vector2(1, 1))
        'waterVertices(4) = New VertexPositionTexture(New Vector3(terrainWidth, waterHeight, -terrainLength), New Vector2(1, 0))

        waterVertexBuffer = New VertexBuffer(GraphicsDevice, VertexPositionTexture.VertexDeclaration, waterVertices.Count, BufferUsage.WriteOnly)
        waterVertexBuffer.SetData(waterVertices)
    End Sub

    Private Sub CreateBillboardVerticesFromList(treeList As List(Of Vector3))
        Dim billboardVertices As VertexPositionTexture() = New VertexPositionTexture(treeList.Count * 6) {}
        Dim i As Integer = -1
        For Each currentV3 As Vector3 In treeList
            i = i + 1
            billboardVertices(i) = New VertexPositionTexture(currentV3, New Vector2(0, 0))
            i = i + 1
            billboardVertices(i) = New VertexPositionTexture(currentV3, New Vector2(1, 0))
            i = i + 1
            billboardVertices(i) = New VertexPositionTexture(currentV3, New Vector2(1, 1))
            i = i + 1
            billboardVertices(i) = New VertexPositionTexture(currentV3, New Vector2(0, 0))
            i = i + 1
            billboardVertices(i) = New VertexPositionTexture(currentV3, New Vector2(1, 1))
            i = i + 1
            billboardVertices(i) = New VertexPositionTexture(currentV3, New Vector2(0, 1))
        Next
        treeVertexBuffer = New VertexBuffer(Device, VertexPositionTexture.VertexDeclaration, billboardVertices.Length, BufferUsage.WriteOnly)
        treeVertexBuffer.SetData(billboardVertices)
    End Sub

    Private Function GenerateTreePositions(treeMap As Texture2D, terrainVertices As VertexMultiTextured()) As List(Of Vector3)
        Dim x As Integer, y As Integer
        Dim treeMapColors As Color() = New Color((treeMap.Width * treeMap.Height) - 1) {}
        Dim terrainHeight As Single
        Dim flatness As Single
        Dim minFlatness As Single
        Dim relx As Single
        Dim rely As Single
        Dim currDetail As Integer
        Dim noiseValueAtCurrentPosition As Single
        Dim treeDensity As Single
        treeMap.GetData(Of Color)(treeMapColors)
        Dim noiseData(treeMap.Width, treeMap.Height) As Integer
        For x = 0 To treeMap.Width - 1
            For y = 0 To treeMap.Height - 1
                noiseData(x, y) = treeMapColors(y + x * treeMap.Height).R
            Next y
        Next x
        Dim treeList As List(Of Vector3) = New List(Of Vector3)
        Dim Random As Random = New Random
        Dim rand1 As Single
        Dim rand2 As Single
        Dim treePos As Vector3
        For x = 0 To terrainWidth - 1
            For y = 0 To terrainLength - 1
                terrainHeight = heightData(x, y)
                If (terrainHeight > 8) And (terrainHeight < 14) Then
                    flatness = Vector3.Dot(terrainVertices(x + y * terrainWidth).Normal, New Vector3(0, 1, 0))
                    minFlatness = CSng(Math.Cos(MathHelper.ToRadians(15)))
                    If flatness > minFlatness Then
                        relx = CSng(x) / CSng(terrainWidth)
                        rely = CSng(y) / CSng(terrainLength)
                        noiseValueAtCurrentPosition = noiseData(CInt(relx * treeMap.Width), CInt(rely * treeMap.Height))
                        If noiseValueAtCurrentPosition > 200 Then
                            treeDensity = 5
                        ElseIf noiseValueAtCurrentPosition > 150 Then
                            treeDensity = 4
                        ElseIf noiseValueAtCurrentPosition > 100 Then
                            treeDensity = 3
                        Else
                            treeDensity = 0
                        End If
                        For currDetail = 0 To CInt(treeDensity - 1)
                            rand1 = CSng(Random.Next(1000)) / 1000
                            rand2 = CSng(Random.Next(1000)) / 1000
                            treePos = New Vector3(CSng(x) - rand1, 0, -CSng(y) - rand2)
                            treePos.Y = heightData(x, y)
                            treeList.Add(treePos)
                        Next currDetail
                    End If
                End If
            Next y
        Next x
        Return treeList
    End Function

    Private Function CreateStaticMap(resolution As Integer) As Texture2D
        Dim rand As Random = New Random
        Dim noisyColors As Color() = New Color((resolution * resolution) - 1) {}
        Dim x As Integer, y As Integer
        For x = 0 To resolution - 1
            For y = 0 To resolution - 1
                noisyColors(x + y * resolution) = New Color(New Vector3(CSng(rand.Next(1000)) / 1000, 0, 0))
            Next y
        Next x
        Dim noiseImage As Texture2D = New Texture2D(Device, resolution, resolution, True, SurfaceFormat.Color)
        noiseImage.SetData(noisyColors)
        Return noiseImage
    End Function

    Private Function SetUpFullscreenVertices() As VertexPositionTexture()
        Dim vertices(3) As VertexPositionTexture
        vertices(0) = New VertexPositionTexture(New Vector3(-1, 1, 0), New Vector2(0, 1))
        vertices(1) = New VertexPositionTexture(New Vector3(1, 1, 0), New Vector2(1, 1))
        vertices(2) = New VertexPositionTexture(New Vector3(-1, -1, 0), New Vector2(0, 0))
        vertices(3) = New VertexPositionTexture(New Vector3(1, -1, 0), New Vector2(1, 0))
        fullScreenVertexBuffer = New VertexBuffer(GraphicsDevice, VertexPositionTexture.VertexDeclaration, vertices.Count, BufferUsage.WriteOnly)
        fullScreenVertexBuffer.SetData(vertices)
        Return vertices
    End Function

    ''' <summary>
    ''' UnloadContent will be called once per game and is the place to unload
    ''' all content.
    ''' </summary>
    Protected Overrides Sub UnloadContent()
        ' TODO: Unload any non ContentManager content here
    End Sub

    ''' <summary>
    ''' Allows the game to run logic such as updating the world,
    ''' checking for collisions, gathering input, and playing audio.
    ''' </summary>
    ''' <param name="gameTime">Provides a snapshot of timing values.</param>
    Protected Overrides Sub Update(ByVal gameTime As GameTime)
        ' Allows the game to exit
        If GamePad.GetState(PlayerIndex.One).Buttons.Back = ButtonState.Pressed Then
            Me.Exit()
        ElseIf Keyboard.GetState.IsKeyDown(Keys.Escape) = True Then
            Me.Exit()
        End If

        Dim timeDifference As Single = CSng(gameTime.ElapsedGameTime.TotalMilliseconds / 1000)
        ProcessInput(timeDifference)

        ' TODO: Add your update logic here
        MyBase.Update(gameTime)
    End Sub

    Private Sub ProcessInput(amount As Single)
        Dim currentMouseState As MouseState = Mouse.GetState
        If currentMouseState = originalMouseState Then
        Else
            If currentMouseState.RightButton = ButtonState.Pressed Then
                Dim xDifference = currentMouseState.X - originalMouseState.X
                Dim yDifference = currentMouseState.Y - originalMouseState.Y
                If currentMouseState.X < Device.Viewport.Width / 3 Then
                    leftrightRot = leftrightRot + rotationSpeed
                End If
                If currentMouseState.X > Device.Viewport.Width - Device.Viewport.Width / 3 Then
                    leftrightRot = leftrightRot - rotationSpeed
                End If
                If currentMouseState.Y < Device.Viewport.Height / 3 Then
                    updownRot = updownRot + rotationSpeed
                End If
                If currentMouseState.Y > Device.Viewport.Height - Device.Viewport.Height / 3 Then
                    updownRot = updownRot - rotationSpeed
                End If
                UpdateViewMatrix()
            End If
        End If
        Dim moveVector As Vector3 = New Vector3(0, 0, 0)
        Dim keyState As KeyboardState = Keyboard.GetState
        If keyState.IsKeyDown(Keys.Up) = True Or keyState.IsKeyDown(Keys.W) = True Then
            moveVector = moveVector + New Vector3(0, 0, -1)
        End If
        If keyState.IsKeyDown(Keys.Down) = True Or keyState.IsKeyDown(Keys.S) = True Then
            moveVector = moveVector + New Vector3(0, 0, 1)
        End If
        If keyState.IsKeyDown(Keys.Right) = True Or keyState.IsKeyDown(Keys.D) = True Then
            moveVector = moveVector + New Vector3(1, 0, 0)
        End If
        If keyState.IsKeyDown(Keys.Left) = True Or keyState.IsKeyDown(Keys.A) = True Then
            moveVector = moveVector + New Vector3(-1, 0, 0)
        End If
        If keyState.IsKeyDown(Keys.Q) = True Then
            moveVector = moveVector + New Vector3(0, 1, 0)
        End If
        If keyState.IsKeyDown(Keys.Z) = True Then
            moveVector = moveVector + New Vector3(0, -1, 0)
        End If
        If keyState.IsKeyDown(Keys.PageUp) = True Then
            waterHeight = CSng(waterHeight + 0.1)
        End If
        If keyState.IsKeyDown(Keys.PageDown) = True Then
            waterHeight = CSng(waterHeight - 0.1)
        End If
        SetUpWaterVertices()

        AddToCameraPosition(moveVector * amount)
    End Sub

    Private Sub AddToCameraPosition(vectorToAdd As Vector3)
        Dim cameraRotation As Matrix = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot)
        Dim rotatedVector As Vector3 = Vector3.Transform(vectorToAdd, cameraRotation)
        cameraPosition = cameraPosition + moveSpeed * rotatedVector
        UpdateViewMatrix()
    End Sub

    Private Sub UpdateViewMatrix()
        Dim cameraRotation As Matrix = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot)
        Dim cameraOriginalTarget As Vector3 = New Vector3(0, 0, -1)
        Dim cameraOriginalUpVector As Vector3 = New Vector3(0, 1, 0)
        Dim cameraRotatedTarget As Vector3 = Vector3.Transform(cameraOriginalTarget, cameraRotation)
        Dim cameraFinalTarget As Vector3 = cameraPosition + cameraRotatedTarget
        Dim cameraRotatedUpVector As Vector3 = Vector3.Transform(cameraOriginalUpVector, cameraRotation)
        viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraRotatedUpVector)
        Dim reflCameraPosition As Vector3 = cameraPosition
        reflCameraPosition.Y = -cameraPosition.Y + waterHeight * 2
        Dim reflTargetPos As Vector3 = cameraFinalTarget
        reflTargetPos.Y = -cameraFinalTarget.Y + waterHeight * 2
        Dim cameraRight As Vector3 = Vector3.Transform(New Vector3(1, 0, 0), cameraRotation)
        Dim invUpVector As Vector3 = Vector3.Cross(cameraRight, reflTargetPos - reflCameraPosition)
        reflectionViewMatrix = Matrix.CreateLookAt(reflCameraPosition, reflTargetPos, invUpVector)
    End Sub

    ''' <summary>
    ''' This is called when the game should draw itself.
    ''' </summary>
    ''' <param name="gameTime">Provides a snapshot of timing values.</param>
    Protected Overrides Sub Draw(ByVal gameTime As GameTime)

        Dim time As Single = CSng(gameTime.TotalGameTime.TotalMilliseconds / 100)
        Dim rs As RasterizerState = New RasterizerState
        rs.CullMode = CullMode.None
        Device.RasterizerState = rs
        DrawRefractionMap()
        DrawReflectionMap()
        'GeneratePerlinNoise(time)
        Device.Clear(ClearOptions.Target, Color.Black, 1, 0)
        DrawSkyDome(viewMatrix)
        DrawTerrain(viewMatrix)
        DrawWater(time)
        DrawBillboards(viewMatrix)

        MyBase.Draw(gameTime)
    End Sub

    Private Sub DrawTerrain(currentViewMatrix As Matrix)
        effect.CurrentTechnique = effect.Techniques("MultiTextured")
        effect.Parameters("xTexture0").SetValue(sandTexture)
        effect.Parameters("xTexture1").SetValue(grassTexture)
        effect.Parameters("xTexture2").SetValue(rockTexture)
        effect.Parameters("xTexture3").SetValue(snowTexture)
        Dim worldMatrix As Matrix = Matrix.Identity
        effect.Parameters("xWorld").SetValue(worldMatrix)
        effect.Parameters("xView").SetValue(currentViewMatrix)
        effect.Parameters("xProjection").SetValue(projectionMatrix)
        effect.Parameters("xEnableLighting").SetValue(True)
        effect.Parameters("xAmbient").SetValue(AmbientLightValue)
        effect.Parameters("xLightDirection").SetValue(New Vector3(-0.5, -1, -0.5))
        For Each pass As EffectPass In effect.CurrentTechnique.Passes
            pass.Apply()
            Device.Indices = terrainIndexBuffer
            Device.SetVertexBuffer(terrainVertexBuffer)
            Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, indices.Length / 3)
        Next
    End Sub

    Private Sub DrawSkyDome(currentViewMatrix As Matrix)
        GraphicsDevice.DepthStencilState = DepthStencilState.None
        Dim worldMatrix As Matrix
        Dim modelTransforms As Matrix() = New Matrix(skyDome.Bones.Count) {}
        skyDome.CopyAbsoluteBoneTransformsTo(modelTransforms)
        Dim wMatrix As Matrix = Matrix.CreateTranslation(0, -0.3, 0) * Matrix.CreateScale(100) * Matrix.CreateTranslation(cameraPosition)
        For Each mesh As ModelMesh In skyDome.Meshes
            For Each currentEffect As Effect In mesh.Effects
                worldMatrix = modelTransforms(mesh.ParentBone.Index) * wMatrix
                currentEffect.CurrentTechnique = currentEffect.Techniques("SkyDome")
                currentEffect.Parameters("xWorld").SetValue(worldMatrix)
                currentEffect.Parameters("xView").SetValue(currentViewMatrix)
                currentEffect.Parameters("xProjection").SetValue(projectionMatrix)
                currentEffect.Parameters("xTexture").SetValue(cloudMap)
                currentEffect.Parameters("xEnableLighting").SetValue(False)
                currentEffect.Parameters("xSkyTopColor").SetValue(SkyTopColor)
                currentEffect.Parameters("xSkyBottomColor").SetValue(SkyBottomColor)
            Next
            mesh.Draw()
        Next
        GraphicsDevice.BlendState = BlendState.Opaque
        GraphicsDevice.DepthStencilState = DepthStencilState.Default
    End Sub

    Private Function CreatePlane(height As Single, planeNormalDirection As Vector3, currentViewMatrix As Matrix, clipSide As Boolean) As Plane
        planeNormalDirection.Normalize()
        Dim planeCoeffs As Vector4 = New Vector4(planeNormalDirection, height)
        If clipSide = True Then
            planeCoeffs = planeCoeffs * -1
        End If
        Dim finalPlane As Plane = New Plane(planeCoeffs)
        Return finalPlane
    End Function

    Private Sub DrawRefractionMap()
        Dim refractionPlane As Plane = CreatePlane(CSng(waterHeight + 1.5), New Vector3(0, -1, 0), viewMatrix, False)
        effect.Parameters("xClipPlane").SetValue(New Vector4(refractionPlane.Normal, refractionPlane.D))
        effect.Parameters("xClipping").SetValue(True)
        Device.SetRenderTarget(refractionRenderTarget)
        Device.Clear(ClearOptions.Target, Color.Black, 1, 0)
        DrawTerrain(viewMatrix)
        Device.SetRenderTarget(Nothing)
        effect.Parameters("xClipping").SetValue(False)
        refractionMap = refractionRenderTarget
    End Sub

    Private Sub DrawReflectionMap()
        Dim reflectionPlane As Plane = CreatePlane(CSng(waterHeight - 0.5), New Vector3(0, -1, 0), reflectionViewMatrix, True)
        effect.Parameters("xClipPlane").SetValue(New Vector4(reflectionPlane.Normal, reflectionPlane.D))
        effect.Parameters("xClipping").SetValue(True)
        Device.SetRenderTarget(reflectionRenderTarget)
        Device.Clear(ClearOptions.Target, Color.Black, 1, 0)
        DrawSkyDome(reflectionViewMatrix)
        DrawTerrain(reflectionViewMatrix)
        effect.Parameters("xClipping").SetValue(False)
        Device.SetRenderTarget(Nothing)
        reflectionMap = reflectionRenderTarget
    End Sub

    Private Sub DrawWater(time As Single)
        effect.CurrentTechnique = effect.Techniques("Water")
        Dim worldMatrix As Matrix = Matrix.Identity
        effect.Parameters("xWorld").SetValue(worldMatrix)
        effect.Parameters("xView").SetValue(viewMatrix)
        effect.Parameters("xReflectionView").SetValue(reflectionViewMatrix)
        effect.Parameters("xProjection").SetValue(projectionMatrix)
        effect.Parameters("xReflectionMap").SetValue(reflectionMap)
        effect.Parameters("xRefractionMap").SetValue(refractionMap)
        effect.Parameters("xWaterBumpMap").SetValue(waterBumpMap)
        effect.Parameters("xWaveLength").SetValue(CSng(0.1))
        effect.Parameters("xWaveHeight").SetValue(CSng(0.3))
        effect.Parameters("xCamPos").SetValue(cameraPosition)
        effect.Parameters("xTime").SetValue(time)
        effect.Parameters("xWindForce").SetValue(CSng(0.001))
        effect.Parameters("xWindDirection").SetValue(windDirection)
        For Each pass As EffectPass In effect.CurrentTechnique.Passes
            pass.Apply()
            Device.SetVertexBuffer(waterVertexBuffer)
            Device.DrawPrimitives(PrimitiveType.TriangleList, 0, CInt(waterVertexBuffer.VertexCount / 3))
        Next
    End Sub

    Private Sub DrawBillboards(currentViewMatrix As Matrix)
        bbEffect.CurrentTechnique = bbEffect.Techniques("CylBillboard")
        bbEffect.Parameters("xWorld").SetValue(Matrix.Identity)
        bbEffect.Parameters("xView").SetValue(currentViewMatrix)
        bbEffect.Parameters("xProjection").SetValue(projectionMatrix)
        bbEffect.Parameters("xCamPos").SetValue(cameraPosition)
        bbEffect.Parameters("xAllowedRotDir").SetValue(New Vector3(0, 1, 0))
        bbEffect.Parameters("xBillboardTexture").SetValue(treeTexture)
        bbEffect.CurrentTechnique.Passes(0).Apply()
        Device.BlendState = BlendState.AlphaBlend
        Device.DepthStencilState = DepthStencilState.DepthRead
        Device.SetVertexBuffer(treeVertexBuffer)
        Device.DrawPrimitives(PrimitiveType.TriangleList, 0, CInt(treeVertexBuffer.VertexCount / 3))

    End Sub

    Private Sub GeneratePerlinNoise(time As Single)
        Device.SetRenderTarget(cloudsRenderTarget)
        Device.Clear(ClearOptions.Target, Color.Black, 1, 0)
        effect.CurrentTechnique = effect.Techniques("PerlinNoise")
        effect.Parameters("xTexture").SetValue(cloudStaticMap)
        effect.Parameters("xOvercast").SetValue(CSng(1.1))
        effect.Parameters("xTime").SetValue(time / 1000)
        For Each pass As EffectPass In effect.CurrentTechnique.Passes
            Device.SetVertexBuffer(fullScreenVertexBuffer)
            Device.DrawPrimitives(PrimitiveType.TriangleList, 0, CInt(fullScreenVertexBuffer.VertexCount / 3))
        Next
        Device.SetRenderTarget(Nothing)
        cloudMap = cloudsRenderTarget
    End Sub

End Class

